fix(hosting): chain entry-assembly and registered XAML metadata providers#144
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes WinUI lifted-XAML metadata resolution for consumer-defined and third-party custom controls by extending Application.Current’s IXamlMetadataProvider chain in ReactorApplication, addressing issue #142.
Changes:
- Added entry-assembly discovery of the consuming app’s compiler-generated
IXamlMetadataProviderand chained it intoReactorApplication’s lookup path. - Introduced
ReactorApp.RegisterControlAssembly(...)APIs to explicitly register third-party metadata providers (with copy-on-write, thread-safe semantics). - Added self-test fixtures and a new “third-party controls” test library to validate both the entry-assembly and explicit-registration paths.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/Reactor/Hosting/ReactorApp.cs |
Adds provider auto-discovery + explicit registration APIs and extends the IXamlMetadataProvider chain used by ReactorApplication. |
tests/Reactor.AppTests.Host/SelfTest/Fixtures/Issue142Fixtures.cs |
Adds regression fixtures covering host-assembly and third-party assembly scenarios. |
tests/Reactor.AppTests.Host/SelfTest/SelfTestFixtureRegistry.cs |
Registers the new issue #142 fixtures in the self-test registry. |
tests/Reactor.AppTests.Host/Themes/Generic.xaml |
Adds a host-side Generic.xaml style to exercise entry-assembly provider discovery. |
tests/Reactor.AppTests.Host/Reactor.AppTests.Host.csproj |
References the new third-party control library for the fixture. |
tests/Reactor.AppTests.ThirdPartyControls/Reactor.AppTests.ThirdPartyControls.csproj |
New WinUI class library project to simulate a third-party control package. |
tests/Reactor.AppTests.ThirdPartyControls/ThirdPartyControlWithPrivateDp.cs |
New custom control with a private DP to reproduce the metadata resolution failure mode. |
tests/Reactor.AppTests.ThirdPartyControls/Themes/Generic.xaml |
New third-party Generic.xaml applying a template using {TemplateBinding MyText}. |
Reactor.sln |
Adds the new third-party controls test project to the solution. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ders WinUI's lifted XAML loader resolves `local:` namespaces and Generic.xaml type/property references through `Application.Current`'s `IXamlMetadataProvider` chain. `ReactorApplication` previously only delegated to Reactor's own generated provider plus a hand-written core stub, so any custom `Control` declared in the consuming app — or in a referenced third-party library when the consumer has no XAML of its own — went unresolved, and the lifted parser took the process down with a `Failed to create a 'Microsoft.UI.Xaml.DependencyProperty' from the text 'MyText'` originate error before the control could render. Two-part fix: 1. Auto-discover the entry assembly's XAML-compiler-generated `XamlMetaDataProvider` and chain it between Reactor's provider and the core stub. Covers the common case where the consumer has any XAML file (which transitively chains every referenced library through the compiler-generated `OtherProviders` list). 2. Add `ReactorApp.RegisterControlAssembly(IXamlMetadataProvider)` and `RegisterControlAssembly(Assembly)` for the no-XAML-consumer case, where there is no compiler-generated chain to ride. Idempotent, thread-safe, copy-on-write snapshot semantics so the hot lookup path needs no locking. Mirrors the documented Win2D / CommunityToolkit pattern in pure WinUI apps. Selftests: `Issue142_CustomControlPrivateDp_Renders` exercises path 1 via a control declared in the host project; `Issue142_ThirdPartyControlPrivateDp_Renders` exercises path 2 via the new `Reactor.AppTests.ThirdPartyControls` library that simulates a real 3P control NuGet. Fixes #142 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses #144 review feedback. `ex.Types` is `Type?[]`, so the previous `.Where(t => t is not null).ToArray()!` pattern relied on a nullable suppression and left a redundant `t is null` check inside the scan loop. `OfType<Type>().ToArray()` produces a true `Type[]`, eliminating both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
20f1016 to
d5dc991
Compare
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Closes #142.
WinUI's lifted XAML loader resolves
local:namespaces and Generic.xaml type/property references throughApplication.Current'sIXamlMetadataProviderchain.ReactorApplicationpreviously only delegated to Reactor's own generated provider plus a hand-written core stub, so any customControldeclared in the consuming app — or in a referenced third-party library when the consumer has no XAML of its own — went unresolved, and the lifted parser took the process down withFailed to create a 'Microsoft.UI.Xaml.DependencyProperty' from the text 'MyText'before the control could render.The bug title talks about "readonly dependency properties" but the failure surfaces for any user-defined property whose declaring type isn't reachable through
Application.Current's metadata chain — the customer's repro just happened to reduce to a control with one private DP.What changed
1. Entry-assembly auto-discovery (
src/Reactor/Hosting/ReactorApp.cs)ReactorApplicationnow scans the entry assembly for any concrete, parameterless-ctorIXamlMetadataProvider(the type the WinUI XAML compiler emits when a project has any XAML file) and chains it between Reactor's own provider and the core stub. Skips Reactor's own generated type to avoid recursion if the entry assembly happens to be Reactor under test hosting. Covers the common case — when the consumer has any XAML file, the compiler-generatedOtherProviderslist transitively chains every referenced library, so this single discovery brings the whole graph in.2. Explicit registration API for 3P libraries
For the no-XAML-consumer case, the compiler-generated chain doesn't exist, so referenced libraries can't be reached transitively. Added:
Idempotent, thread-safe, copy-on-write snapshot semantics so the hot lookup path on the UI thread needs no locking. The
Assemblyoverload reflects out the compiler-generated provider type and instantiates it. Mirrors the Win2D / CommunityToolkit setup pattern in pure WinUI.3. Tests
Issue142_CustomControlPrivateDp_Renders— entry-assembly path. Custom control + Generic.xaml inReactor.AppTests.Hostitself, exercises the auto-discovery.Issue142_ThirdPartyControlPrivateDp_Renders— registration path. New standalone librarytests/Reactor.AppTests.ThirdPartyControls/simulates a 3P NuGet (custom control with private DP + Themes/Generic.xaml +{TemplateBinding MyText}). Fixture callsRegisterControlAssembly(...)and asserts both the registration list and the rendered template-bound text.The 3P fixture's failure mode (process crash without registration in a no-XAML consumer) was verified manually with a temporary throwaway app; the fixture itself passes either way in this test host because the host's own
Themes/Generic.xamltriggers the auto-chain. The fixture comment explains this and asserts the registered-provider list is non-empty so the API is at least exercised.Test plan
dotnet build Reactor.sln -c Debug— clean.Reactor.AppTests.Host.exe --self-test— 2214/2214 ok.dotnet test tests/Reactor.Tests— 6793/0 fail.RegisterControlAssembly, process crashes without it (matches issue [Bug] Controls with readonly dependency properties are not supported #142 originate error).🤖 Generated with Claude Code